package studia.figlewicz.math.library;

import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

public class ModelBlackScholes extends Model {
	
	public final static String[] nazwyZmiennychObjasniajacych = {
		ModelBlackScholes.ZMIENNA_OBJASNIAJACA_STOPA_PROCENTOWA,
		ModelBlackScholes.ZMIENNA_OBJASNIAJACA_ZMIENNOSC,
		ModelBlackScholes.ZMIENNA_OBJASNIAJACA_CENA_POCZATKOWA,
		ModelBlackScholes.ZMIENNA_OBJASNIAJACA_ZMIENNA_LOSOWA
	};
	public final static String ZMIENNA_OBJASNIAJACA_STOPA_PROCENTOWA = Stale.MODEL_BLACK_SCHOLES_ZM_R;
	public final static String ZMIENNA_OBJASNIAJACA_ZMIENNOSC = Stale.MODEL_BLACK_SCHOLES_ZM_SIGMA;
	public final static String ZMIENNA_OBJASNIAJACA_CENA_POCZATKOWA = Stale.MODEL_BLACK_SCHOLES_ZM_S0;
	public final static String ZMIENNA_OBJASNIAJACA_ZMIENNA_LOSOWA = Stale.MODEL_BLACK_SCHOLES_ZM_EPS;
	public final static String[] nazwyZmiennychObjasnianych = {
		ModelBlackScholes.ZMIENNA_OBJASNIANA_BLACK_SCHOLES_PROCESS
	};
	public final static String ZMIENNA_OBJASNIANA_BLACK_SCHOLES_PROCESS= Stale.MODEL_BLACK_SCHOLES_WYNIK_S;
	
	private MetodaInterpolacji interpolacja;
	
	private Double cenaPoczatkowaS0 = null;

	
	// konstruktory
	public ModelBlackScholes(MetodaInterpolacji interpolacja) {
		super();
		for (String zm : nazwyZmiennychObjasniajacych) {
			zmienneObjasniajace.put(zm, null);
		}
		this.interpolacja = interpolacja;
	}
	

	// gettery i settery
	public MetodaInterpolacji getInterpolacja() {
		return interpolacja;
	}
	public void setInterpolacja(MetodaInterpolacji interpolacja) {
		this.interpolacja = interpolacja;
	}
	@Override
	public String[] getNazwyZmiennychObjasnianych() {
		return nazwyZmiennychObjasnianych;
	}
	@Override
	public String[] getNazwyZmiennychObjasniajacych() {
		return nazwyZmiennychObjasniajacych;
	}

	
	// metody
	@Override
	public Map<String, TrajektoriaProcesu> utworzBuforWynikuProjekcji() {
		Map<String, TrajektoriaProcesu> resoult = new HashMap<String, TrajektoriaProcesu>();
		for (String zm : nazwyZmiennychObjasnianych) {
			resoult.put(zm, new TrajektoriaProcesu());
		}
		return resoult;
		
	}
	@Override
	public void inicjalucujProjekcje(Date poczatekSymulacji) {
		ZmiennaObjasniajaca cenaSpot = zmienneObjasniajace.get(ZMIENNA_OBJASNIAJACA_CENA_POCZATKOWA);
		
		Map<Date,Object> temp = new LinkedHashMap<Date,Object>();
		
		for (Date d : cenaSpot.getPunkty().keySet())
			temp.put(d, new Object());
		Date dataSpot = Utils.getOstatniPrzed(temp, new Date(poczatekSymulacji.getTime()+1));
		
		this.cenaPoczatkowaS0 = cenaSpot.getPunkty().get(dataSpot);
		this.poczatekProjekcji = poczatekSymulacji;
	}
	@Override
	public Map<String, Double> wyznaczKrokProjekcji(Date dzienProjekcji,  Map<String, TrajektoriaProcesu> dotychczasoweTrajektorie) {
		Map<String, Double> resoult = new HashMap<String, Double>();
		Double value = null;
		ZmiennaObjasniajaca stopaProcentowa = zmienneObjasniajace.get(ZMIENNA_OBJASNIAJACA_STOPA_PROCENTOWA);
		ZmiennaObjasniajaca zmiennosc = zmienneObjasniajace.get(ZMIENNA_OBJASNIAJACA_ZMIENNOSC);
		ZmiennaObjasniajaca cenaSpot = zmienneObjasniajace.get(ZMIENNA_OBJASNIAJACA_CENA_POCZATKOWA);
		ZmiennaObjasniajaca zmiennaLosowa = zmienneObjasniajace.get(ZMIENNA_OBJASNIAJACA_ZMIENNA_LOSOWA);
		
		if (stopaProcentowa != null && zmiennosc != null && cenaSpot != null && zmiennaLosowa != null) {
			TrajektoriaProcesu procesCeny = dotychczasoweTrajektorie.get(ZMIENNA_OBJASNIANA_BLACK_SCHOLES_PROCESS);
			
			Date poczatek = null;
			double S0 = 0;
			if (procesCeny.getPukntySymulacji().size() == 0) {
				poczatek = this.poczatekProjekcji;
				S0 = this.cenaPoczatkowaS0;
			}
			else {
				PunktTrajektorii ostatniPunkt = procesCeny.koniecTrajektorii();
				poczatek = ostatniPunkt.getData();
				S0 = ostatniPunkt.getWartosc().doubleValue();
			}

			double r = integral(stopaProcentowa, poczatek, dzienProjekcji,1);
			double sigma = integral(zmiennosc, poczatek, dzienProjekcji,2);
			
			Double e = null;
			for (Double d : zmiennaLosowa.getPunkty().values()) {
				if (e == null) {
					e = d;
					break;
				}
			}
			
			double BSprocess = getBlackScholesValue(r, sigma, e.doubleValue());
			double currentBSprocess = S0 * BSprocess;
			value = new Double(currentBSprocess);
		}
		resoult.put(ZMIENNA_OBJASNIANA_BLACK_SCHOLES_PROCESS, value);
		
		return resoult;
	}
	
	
	// metody prywatne
	@Override
	protected boolean isObjasniajaca(String nazwa) {
		boolean resoult = false;
		for (String zm : nazwyZmiennychObjasniajacych) {
			if (zm.equals(nazwa)) {
				resoult = true;
				break;
			}
		}
		
		return resoult;
	}
	@Override	
	protected boolean isObjasniana(String nazwa) {
		boolean resoult = false;
		for (String zm : nazwyZmiennychObjasnianych) {
			if (zm.equals(nazwa)) {
				resoult = true;
				break;
			}
		}
		
		return resoult;
	}
	private double getBlackScholesValue(
				double stopaProcentowa, 
				double zmiennosc, 
				double zmiennaLosowa) {
		double resoult = 0;
		
		double dryft = stopaProcentowa - 1/2 * zmiennosc;
		double szum = Math.sqrt(zmiennosc) * zmiennaLosowa;
		
		resoult = Math.exp(dryft + szum);

		return resoult;
	}
	private double integral(ZmiennaObjasniajaca zmienna, Date poczatek, Date koniec, int potega) {
		double resoult = 0;
		
		LinkedHashMap<Date, Double> punkty = zmienna.getPunkty();
		
		Double val1 = Utils.wartoscWygladzona(punkty, poczatek, interpolacja);
		Double val2 = Utils.wartoscWygladzona(punkty, koniec, interpolacja);
		double podsatwa = Utils.getYearFractionStandard(poczatek,koniec);
		
		if (potega > 1) {
			double srednia = 1/2 * (val1.doubleValue() + val2.doubleValue());
			double oldval1 = val1.doubleValue();
			double oldval2 = val1.doubleValue();
			for (int i=1; i<potega; i++) {
				val1 = val1.doubleValue() * oldval1;
				val2 = val2.doubleValue() * oldval2;
			}
			if (val1.doubleValue() < val2.doubleValue()) {
				resoult = podsatwa * 1/2 * (val1.doubleValue() + srednia);
				resoult = resoult + podsatwa * 1/2 * (srednia + val2.doubleValue());
			}
			else {
				resoult = podsatwa * 1/2 * (val2.doubleValue() + srednia);
				resoult = resoult + podsatwa * 1/2 * (srednia + val1.doubleValue());
			}
		}
		else
			resoult = podsatwa * 1/2 * (val1.doubleValue() + val2.doubleValue());
		
		return resoult;
	}
	
	
}
